// Thud - a dynamic object-oriented language
// Copyright (C) 2009,2010 David H. Hovemeyer <david.hovemeyer@gmail.com>

// This program is free software: you can redistribute it and/or modify
// it under the terms of the GNU General Public License as published by
// the Free Software Foundation, either version 3 of the License, or
// (at your option) any later version.
// 
// This program is distributed in the hope that it will be useful,
// but WITHOUT ANY WARRANTY; without even the implied warranty of
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
// GNU General Public License for more details.
// 
// You should have received a copy of the GNU General Public License
// along with this program.  If not, see <http://www.gnu.org/licenses/>.

#include <cstdio> // debugging
#include <cassert>
#include <memory>
#include "util/DataInputStream.h"
#include "util/DataOutputStream.h"
#include "vm/ClassFormatException.h"
#include "vm/VersionMismatchException.h"
#include "vm/Magic.h"
#include "vm/ConstantPool.h"
#include "vm/ConstantString.h"
#include "vm/Field.h"
#include "vm/Method.h"
#include "vm/Class.h"

//#define DEBUG_LOAD(args...) printf(args)
#define DEBUG_LOAD(args...)

const unsigned MAX_FIELDS = 256;
const unsigned MAX_METHODS = 256;

Class::Class()
	: m_flags(0)
	, m_constantPool(new ConstantPool())
{
}

Class::Class(uint32_t flags, const std::wstring &clsName)
	: m_flags(flags)
	, m_clsName(clsName)
	, m_constantPool(new ConstantPool())
{
	// constant 0 must be the name of the class
	m_constantPool->addAndAdopt(new ConstantString(clsName));

	assert(m_constantPool->getNumConstants() == 1);
}

Class::~Class()
{
	delete m_constantPool;

	for (std::vector<Field *>::iterator i = m_fieldList.begin(); i != m_fieldList.end(); i++) {
		delete *i;
	}

	for (std::vector<Method *>::iterator i = m_methodList.begin(); i != m_methodList.end(); i++) {
		delete *i;
	}
}

void Class::setFlags(uint32_t flags)
{
	m_flags = flags;
}

uint32_t Class::getFlags() const
{
	return m_flags;
}

const std::wstring &Class::getClassName() const
{
	return m_clsName;
}

void Class::setClassName(const std::wstring &clsName)
{
	m_clsName = clsName;
}

void Class::addAndAdoptField(Field *field)
{
	m_fieldList.push_back(field);
}

void Class::addAndAdoptMethod(Method *method)
{
	m_methodList.push_back(method);
}

std::vector<Field *>& Class::getFields()
{
	return m_fieldList;
}

std::vector<Method *>& Class::getMethods()
{
	return m_methodList;
}

Method *Class::findMethod(const std::wstring &methodName)
{
	for (MethodList::iterator i = m_methodList.begin(); i != m_methodList.end(); i++) {
		if ((*i)->getName().compare(methodName) == 0) {
			return *i;
		}
	}
	throw VirtualMachineException(L"Unknown method");
}

bool Class::hasMethod(const std::wstring &methodName)
{
	try {
		findMethod(methodName);
		return true;
	} catch (VirtualMachineException &e) {
		return false;
	}
}

void Class::read(DataInputStream *in)
{
	uint32_t magic = in->readInt();

	if (magic != CLASS_MAGIC) {
		throw ClassFormatException(L"Invalid magic number");
	}

	uint32_t major = in->readInt();
	uint32_t minor = in->readInt();

	DEBUG_LOAD("read magic, major, minor\n");

	if (major != CLASS_MAJOR || minor != CLASS_MINOR) {
		throw VersionMismatchException(L"Version mismatch");
	}

	m_flags = in->readInt();

	DEBUG_LOAD("read flags\n");

	m_constantPool->read(in);

	DEBUG_LOAD("read constant pool\n");

	// constant 0 must be the name of the class
	Constant *clsName = m_constantPool->getConstant(0);
	if (clsName->getType() != CONSTANT_STRING) {
		throw ClassFormatException(L"Constant 0 is not a string");
	}
	m_clsName = clsName->asString(m_constantPool);

	uint32_t numFields = in->readInt();
	if (numFields > MAX_FIELDS) {
		throw ClassFormatException(L"Too many fields");
	}
	IPersistent::readAll(m_fieldList, numFields, in);

	uint32_t numMethods = in->readInt();
	if (numMethods > MAX_METHODS) {
		throw ClassFormatException(L"Too many methods");
	}
	IPersistent::readAll(m_methodList, numMethods, in);
}

void Class::write(DataOutputStream *out) const
{
	out->writeInt(CLASS_MAGIC);
	out->writeInt(CLASS_MAJOR);
	out->writeInt(CLASS_MINOR);

	out->writeInt(m_flags);

	m_constantPool->write(out);

	out->writeInt(m_fieldList.size());
	IPersistent::writeAll(m_fieldList, out);

	out->writeInt(m_methodList.size());
	IPersistent::writeAll(m_methodList, out);
}
